package com.small.nacos.client.naming.net;

import com.alibaba.nacos.api.SystemPropertyKeyConst;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.CommonParams;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.common.constant.HttpHeaderConsts;
import com.alibaba.nacos.common.http.HttpRestResult;
import com.alibaba.nacos.common.http.client.NacosRestTemplate;
import com.alibaba.nacos.common.http.param.Header;
import com.alibaba.nacos.common.http.param.Query;
import com.alibaba.nacos.common.utils.*;
import com.fasterxml.jackson.databind.JsonNode;
import com.small.nacos.client.naming.beat.BeatInfo;
import com.small.nacos.client.naming.utils.UtilAndComs;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.util.*;


/**
 * @Author zhoujin
 * @Date 2022/7/21 20:30
 */

public class NamingProxy{

    private final Logger LOGGER = LoggerFactory.getLogger(NamingProxy.class);

    private static final NacosRestTemplate nacosRestTemplate = NamingHttpClientManager.getInstance().getNacosRestTemplate();

    private final String namespaceId;

    private List<String> serverList;

    private static final int DEFAULT_SERVER_PORT = 8858;

    private int serverPort = DEFAULT_SERVER_PORT;

    public NamingProxy(String namespaceId,String serverList) {
        this.namespaceId = namespaceId;
        this.setServerPort(DEFAULT_SERVER_PORT);
        if (StringUtils.isNotEmpty(serverList)) {
            this.serverList = Arrays.asList(serverList.split(","));
        }
    }

    public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {

        final Map<String, String> params = new HashMap<String, String>(16);
        params.put(CommonParams.NAMESPACE_ID, namespaceId);
        params.put(CommonParams.SERVICE_NAME, serviceName);
        params.put(CommonParams.GROUP_NAME, groupName);
        params.put("ip", instance.getIp());
        params.put("port", String.valueOf(instance.getPort()));
        params.put("ephemeral", String.valueOf(instance.isEphemeral()));
        reqApi(UtilAndComs.nacosUrlInstance, params, HttpMethod.POST);
    }

    public String reqApi(String api, Map<String, String> params, String method) throws NacosException {
        return reqApi(api, params, Collections.EMPTY_MAP, method);
    }

    public String reqApi(String api, Map<String, String> params, Map<String, String> body, String method)
            throws NacosException {
        return reqApi(api, params, body, getServerList(), method);
    }

    public String reqApi(String api, Map<String, String> params, Map<String, String> body, List<String> servers,
                         String method) throws NacosException {

        params.put(CommonParams.NAMESPACE_ID, getNamespaceId());

        if (CollectionUtils.isEmpty(servers)){
            throw new NacosException(NacosException.INVALID_PARAM, "no server available");
        }

        NacosException exception = new NacosException();

        Random random = new Random(System.currentTimeMillis());
        int index = random.nextInt(servers.size());
        for (int i = 0; i < servers.size(); i++) {
            String server = servers.get(index);

            try {
                return callServer(api,params,body,server,method);
            }catch (NacosException e){
                exception = e;
                LOGGER.debug("request {} failed.", server, e);
            }
            index = (index+1) % servers.size();

        }

        LOGGER.error("request: {} failed, servers: {}, code: {}, msg: {}", api, servers, exception.getErrCode(),
                exception.getErrMsg());

        throw new NacosException(exception.getErrCode(),
                "failed to req API:" + api + " after all servers(" + servers + ") tried: " + exception.getMessage());
    }

    public String callServer(String api, Map<String, String> params, Map<String, String> body, String curServer,
                             String method) throws NacosException {
        Header header = builderHeader();

        String url;
        if (curServer.startsWith(UtilAndComs.HTTPS) || curServer.startsWith(UtilAndComs.HTTP)) {
            url = curServer + api;
        } else {
            curServer = curServer + UtilAndComs.IP_PORT_SPLITER + serverPort;
            url = NamingHttpClientManager.getInstance().getPrefix() + curServer + api;
        }

        try {
            HttpRestResult<String> restResult = nacosRestTemplate
                    .exchangeForm(url, header, Query.newInstance().initParams(params), body, method, String.class);

            if (restResult.ok()) {
                return restResult.getData();
            }
            if (HttpStatus.SC_NOT_MODIFIED == restResult.getCode()) {
                return StringUtils.EMPTY;
            }
            throw new NacosException(restResult.getCode(), restResult.getMessage());
        } catch (NacosException ex) {
            LOGGER.warn("[NA] failed to request cause of inner exception");
            throw ex;
        } catch (ConnectException connectException) {
            LOGGER.warn("[NA] failed to request cause of connection exception");
            throw new NacosException(NacosException.SERVER_ERROR, connectException);
        } catch (SocketTimeoutException timeoutException) {
            LOGGER.warn("[NA] failed to request cause of connection time out exception");
            throw new NacosException(NacosException.SERVER_ERROR, timeoutException);
        } catch (Exception unknownEx) {
            LOGGER.error("[NA] failed to request cause of unknown reason", unknownEx);
            throw new NacosException(NacosException.SERVER_ERROR, unknownEx);
        }
    }

    public static Header builderHeader() {
        Header header = Header.newInstance();
        header.addParam(HttpHeaderConsts.CLIENT_VERSION_HEADER, VersionUtils.version);
        header.addParam(HttpHeaderConsts.USER_AGENT_HEADER, "123");
        header.addParam(HttpHeaderConsts.ACCEPT_ENCODING, "gzip,deflate,sdch");
        header.addParam(HttpHeaderConsts.CONNECTION, "Keep-Alive");
        header.addParam(HttpHeaderConsts.REQUEST_ID, UuidUtils.generateUuid());
        header.addParam(HttpHeaderConsts.REQUEST_MODULE, "Naming");
        return header;
    }

    public String getNamespaceId() {
        return namespaceId;
    }

    private List<String> getServerList() {
        return serverList;
    }

    public void setServerPort(int serverPort) {
        this.serverPort = serverPort;

        String sp = System.getProperty(SystemPropertyKeyConst.NAMING_SERVER_PORT);
        if (StringUtils.isNotBlank(sp)) {
            this.serverPort = Integer.parseInt(sp);
        }
    }

    public String queryList(String serviceName, String clusters) throws NacosException {

        final Map<String, String> params = new HashMap<String, String>(16);
        params.put(CommonParams.NAMESPACE_ID, namespaceId);
        params.put(CommonParams.SERVICE_NAME, serviceName);
        params.put("clusters", clusters);
        return reqApi(UtilAndComs.nacosUrlInstance + "/list", params, HttpMethod.GET);
    }

    public void deregisterService(String serviceName, Instance instance) throws NacosException {

        final Map<String, String> params = new HashMap<String, String>(8);
        params.put(CommonParams.NAMESPACE_ID, namespaceId);
        params.put(CommonParams.SERVICE_NAME, serviceName);
        params.put(CommonParams.CLUSTER_NAME, instance.getClusterName());
        params.put("ip", instance.getIp());
        params.put("port", String.valueOf(instance.getPort()));
        params.put("ephemeral", String.valueOf(instance.isEphemeral()));

        reqApi(UtilAndComs.nacosUrlInstance, params, HttpMethod.DELETE);
    }

    public JsonNode sendBeat(BeatInfo beatInfo, Boolean lightBeatEnabled) throws NacosException {

        Map<String, String> params = new HashMap<String, String>(8);
        Map<String, String> bodyMap = new HashMap<String, String>(2);
        //判断是否发送过心跳信号
        if (!lightBeatEnabled) {
            bodyMap.put("beat", JacksonUtils.toJson(beatInfo));
        }

        params.put(CommonParams.NAMESPACE_ID, namespaceId);
        params.put(CommonParams.SERVICE_NAME, beatInfo.getServiceName());
        params.put(CommonParams.CLUSTER_NAME, beatInfo.getCluster());
        params.put("ip", beatInfo.getIp());
        params.put("port", String.valueOf(beatInfo.getPort()));

        String result = reqApi(UtilAndComs.nacosUrlBase + "/instance/beat", params, bodyMap, HttpMethod.PUT);
        return JacksonUtils.toObj(result);
    }
}
